4.1 iOS设计模式简介
4.1.1 编码是门艺术
4.1.2 设计模式的基本原则
- 开闭原则:一个模块的修改应该对扩展开发,对修改关闭
- 里氏代换原则:子类、父类可以替换使用
- 依赖倒转原则:抽象比依赖细节,细节依赖抽象
- 接口隔离原则:只做和接口相关的必要的事情,不做和接口不相关的事情
- 合成/聚合复用:从一个类扩展出新的方法时,尽量不要通过继承实现,而是在新的类中的方法中将原来的类作为一个组件调用
4.1.3 设计模式的类型
4.2 适配器
4.2.1 何为适配器模式
课程说明:本课时通过现实生活中遇到的电源适配问题,来讲解何为适配器模式。
说明:苹果的电源适配器,可以将各种输入电压频率转为一致的电压频率为设备供电,从而解决国家电压频率不一致问题
- 类适配器:比之于一般的电源适配器,在不同国家要购买相应的电源适配器
- 对象适配器:比之于苹果的电源适配器,能自动适应各个国家的情况
4.2.2 数据直接适配带来的困境
课程说明:本课时讲解开发过程中遇到的最常见的几种数据适配问题的弊端,并配以代码示例,来引申出为何要用适配器模式。
- 直接赋值的弊端
- 用对象赋值的灵活性问题
- 如何降低数据层与视图层的耦合度
4.2.3 使用适配器模式
课程说明:本课时讲解用适配器模式来优化名片视图加载数据的问题。
- 创建抽象适配器模式
- 适配器与视图层建立输出联系
- 类适配器与对象适配器
4.2.3.1 类适配器
说明:为每种数据模型都创建相应的适配器,用到哪种数据就使用相应的适配器。
1 | // 1. 创建商务名片实例 |
4.2.3.2 对象适配器
说明:每种数据模型都使用同一个适配器,适配器中承担为每种数据模型都提供适配的任务。
1 | // 1. 创建商务名片实例 |
4.2.4 适配器模式的优缺点
优点:降低数据层和视图层之间的耦合度
缺点:对不理解适配器模式的人可读性不好
4.3 策略
4.3.1 if - else 的问题
课程说明:程序中出现了大量 if - else 之后,出现的阅读障碍以及维护的困境。
4.3.2 策略模式的原理
课程说明:策略模式的基本原理以及定义的条件。
4.3.3 策略模式的使用
课程说明:怎样将策略模式用到
UITextField
的验证逻辑上。
- 如何抽象出策略
- 制定协议来维护输出信息
ViewController.m
1 |
|
4.3.4 策略模式的优缺点
优点:避免大量if-else
的使用,精简逻辑,可维护性高
缺点:使用场景受限,使用之前就需要确定对应的策略。
4.4 观察者
4.4.1 如何订阅一本杂志
课程说明:本课时通过讲解如何订阅杂志的生活实例,来讲解何为观察者模式。
4.4.1.1 如何订阅一本杂志
- 通过邮局或网络查询刊物的编号
- 找到后,提供个人住址等相关信息
- 发行商在一般每月的固定时间发送刊物
4.4.2 通知中心的抽象设计
课程说明:本课时根据订阅杂志的实例,讲解如何将抽象的模型转换成代码。
- 如何抽象接口
- 对订阅对象的约束:订阅对象必须采纳相应的协议
- 针对接口编程:先根据目标功能定好接口,然后再具体实现
SubscriptionServiceCenter.h
1 |
|
4.4.3 实现通知中心
课程说明:本课时将实现通知中心,并讲解
NSHashTable
与NSParameterAssert
的使用。
注意:NSHashTable
和NSSet
类似,但前者可以不会强引用集合中的元素。
- 对象持有的问题
描述:书刊和用户之间的联系导致用户实例无法被释放
解决:用NSHashTable
实现weak
引用 - 参数的严格验证
描述:比如参数值不允许值为nil
4.4.3.1 实现通知中心类
SubscriptionServiceCenter.m
1 |
|
4.4.3.2 使用通知中心
SubscriptionServiceCenterProtocol.h
1 |
|
ViewController.m
1 |
|
4.4.4 KVO与通知中心
课程说明:本课时简单介绍下 Cocoa 框架中的 KVO 与通知中心的使用。
4.4.4.1 Cocoa框架的通知中心
说明:
Cocoa
框架提供了自己的通知中心API
,该通知中心就是通过观察者模式实现的。
注意:观察者被释放时需要手动将观察者从订阅号中移除,否则会存在内存泄漏(通知中心会继续持有对观察者的引用)。
ViewController.m
1 |
|
4.4.4.2 KVO机制的观察者模式
说明:
KVO
机制本身就是一种观察者模式。
注意:观察者被释放时需要手动将观察者从订阅号中移除,否则会存在内存泄漏(通知中心会继续持有对观察者的引用)。
通知中心
Model.h
1 |
|
Model.m
1 |
|
演示
ViewController.m
1 |
|
4.5 原型模式和外观模式
4.5.1 原型模式
4.5.1.1 模版的用处
用途:当要创建的对象有很多相似的细节的时候,就可以通过制作模版简化工作。
4.5.1.2 原型模式的原理
说明:定义一个协议,采纳该协议个类要实现
clone
方法,也就是具备拷贝自身的能力。
案例
PrototypeCopyProtocol.h
1 |
|
StudentModel.m
1 |
|
ViewController.m
1 |
|
4.5.1.3 NSCoping协议的使用细节
说明:
Cocoa
通过NSCoping
协议为原型模式提供了支持。
注意:
- 深拷贝与浅拷贝
- 不支持
NSCopying
协议的对象
4.5.2 外观模式
4.5.2.1 如何去一个指定的地方
说明:用来说明外观模式的一个显示场景
- 自驾:自己负责路线相关的各种事务
- 坐火车:将细节交给黑盒
4.5.2.2 外观模式的原理
说明:就是将细节封装了起来,使用时不需要了解细节
4.5.2.3 如何绘制复杂的图形
4.6 装饰
不改变原类,动态地扩展原类的功能
4.6.1 照片与相框
课程说明:通过照片与相框之间的联系,来讲解何为装饰模式。
4.6.2 装饰模式的原理
基本原理
装饰起模式的优点
装饰模式的使用场景
不知道一个类的实现细节,只知道相关的接口,又需要扩展这个类的功能。
4.6.3 实现装饰模式
课程说明:通过一个扩展游戏机手柄行为的事例,来实现装饰模式。
4.6.4 category的使用
课程说明:讲解
Cocoa
框架已经实现的装饰模式,并延伸讲解下如何给category
添加属性。
说明:利用Objective-C
的动态运行时分配机制,你可以为现有的类添加方法或计算属性,这种机制称为类别(category
)。
注意:和装饰模式不同,这种扩展方式会导致原类型的改变。
- 原类型
GamePad.h
1 |
|
GamePod.m
1 |
|
- 扩展
GamePad+Coin.h
1 |
|
GamePad+Coin.m
1 |
|
- 使用扩展后的GamePod
ViewController.m
1 |
|
4.7 工厂
4.7.1 制造手机与使用手机
4.7.2 简单工厂
说明:将“产品”的生产任务封装在“工厂中”
- 简化生产流程
- 隔离生产产品的细节
- 不同类型产品之间有着一些共同的功能
- 一个具体的工厂
案例:通过简单工厂获取手机实例
源码结构
类图:通过
使用
ViewController.m
1 |
|
4.7.3 抽象工厂
说明:
客户端
通过抽象工厂
创建具体的工厂
,然后通过具体的工厂
获取产品。相比简单工厂,抽象工厂多了一层抽象,在很多场景中可以比简单工厂更好地解耦。
- 抽象工厂原理
- 抽象工厂抽象在哪里
- 抽象工厂使用场景
案例:通过工厂管理器获取定制的具体工厂,并通过具体的工厂获取产品
源码结构
类图
使用
1 | // 获取工厂 |
4.7.4 Cocoa 框架中的 NSNumber
说明:
NSNumber
本身是一个抽象工厂类,直接通过该类init
一个实例会获得一个nil
。NSNumber
提供了一系列工厂方法来获取针对具体数值类型的工厂。
注意:NSNumber
是针对数值
的抽象工厂,产品是各种数值
类型,而且这些数值
之间可以进行类型转换。
1 | // 由于NSNumber是一个抽象工厂,这里只会返回一个 nil |
4.8 桥接
说明:两类存在通信的实体,其实例实现各自的协议,两类实体的实例就可以正确通信。这种设计方式解耦了通信协议的定义和具体实例的实现。
4.8.1 遥控器与电视机
例子:为遥控器和电视机分别定义了协议,采纳了协议的具体的遥控器和电视机之间只需完成各自的实现。
4.8.2 桥接模式原理
目的: 把抽象层次结构从具体的实现分离出来,使其能够独立变更。
抽象层:定义了共客户端使用的上层抽象接口。
实现层:定义了供抽象层使用的底层接口。
桥接:实现类的引用被封装到抽象层的实例中,桥接就形成了。
4.8.3 设计游戏机模拟器
- 游戏机模拟器的功能定义
- 按钮协议的制定
- 游戏机模拟器的实现
案例
1 | /* GBA系统 + GBA执行器 */ |
4.9 代理
4.9.1 代理模式
说明:当一个对象和另一个对象必然耦合的时候,为了降低这种耦合,可以选择和代理对象耦和。代理对象可以是实现了代理协议的任意对象,由于采纳代理协议的对象相比具体对象更灵活且可配置,从而降低了耦和度。
注意:代理对象建议修饰为weak
,从而有助于减低消耗。
案例:顾客和经销商
顾客的购买行为代理给经销商
Customer.h
1 |
|
Cosutomer.m
1 |
|
Dealer.h
1 |
|
Dealer.m
1 |
|
使用
ViewController.m
1 |
|
4.9.2 代理与协议
代理对象
本质上是让对象采纳协议
的一种特殊情况。和单纯要求对象采纳某种协议相比,代理对象承担者完成代理任务的同时降低与被调用者之间的耦和度的职能。
案例:单纯使用协议
TCPProtocol.h
说明:定义协议
1 |
|
Model.h
说明:采纳协议
1 |
|
Model.m
说明:实现协议
1 |
|
ViewCnotroller.m
1 |
|
4.9.3 用NSProxy 实现的代理模式
NSProxy
:没有父类,和NSObject
是一个级别的
用途:可以方便地向代理对象发送一系列消息。
案例
1 | // 创建代理 |
4.10 单例
4.10.1 单例模式
单例模式的基本原理
单例模式用以解决何种问题
保证某个类只会存在一个全局的实例,全局共享
单例模式的优缺点
- 优点:共享实例
- 缺点:使使用到单例实例的类之间耦和在一起
案例:存储用户信息的单例类
UserInfoManagerCenter.h
1 |
|
UserInfoManagerCenter.m
1 |
|
使用
1 | UserInfoManagerCenter *center = [UserInfoManagerCenter managerCenter]; |
4.10.2 编写严格的单例
要点
- 防止继承:在
init
构造器中判断当前类名,当子类调用init
或工厂方法创建实例时中断程序- 确保实例对象只出现一个:限制
init
构造器只能在获取实例的工厂方法中调用,而且只调用一次
UserInfoManagerCenter.h
1 |
|
UserInfoManagerCenter.m
1 |
|
1 | /* 正确获取单例的方法 */ |
4.10.3 优化本地存储
要点
- 用单例设计存储数据接口
- 用单例接口隔离实现细节
- 在单例提供接口的基础上进行上层封装
扩展:使用 FastCoding 替代
NSCoding
存储数据
注意:启用ARC
时FastCoding
运行会比较慢,建议关闭
- 文件启用了
ARC
是代码给出了警告- 通过关闭
FastCoder
对应的文件的ARC
清除警告
1 | // 将相对复杂的对象数据存储到用户首选项(借由对象的扩展) |
4.11 备忘录
4.11.1 如何存储记录
课程说明:用几个简单的示例来说明如何存储纪录,并引申出什么是备忘录模式。
- 存储记录的必要性
- 记录的唯一标识符
- 存储纪录和取出记录
4.11.2 备忘录模式
- 设计存储中心
- 制定存储接口
- 实现存储机制
4.11.3 优化存储方案
- 统一存储规范
- 实现灵活多变的存储机制
1 | // 创建对象 |
4.11.4 恢复 UIView 的状态
摘要:可以借助上面实现的备忘录模式存储/恢复
UIView
的状态。
存储UIView的问题
UIView
实现了NSCoding
协议,但没有实现NSCopy
协议。所以,UIView
可以被存储下来,但无法拷贝副本NSCoding
协议只能存储对象的所有数据,不能仅存储特定的状态数据(比如frame
值)
使用备忘录模式
1 | // 创建 UIView |
4.12 生成器
说明:有时,构建某些对象有多种不同的方式。如果这些逻辑包含在构建这些对象的类的单一方法中,构建的逻辑会非常的荒唐(例如,针对各种构建需求的一大片
if-else
或者switch-case
语句)。如果能够把构建过程分解为客户-指导者-生成器
的关系,那么过程将更容易管理与复用。针对此类关系的设计模式称为生成器
。
4.12.1 建造房子的承包商
课程说明:本课时用一个生活中建造房子例子说明并引申出何为生成器模式
- 如何建造房子
- 建造过程的模块化处理
- 不同模块找不同的承包商
使用生成器模式的优点:
- 不需要知道细节
- 模块化处理
- 很好的组合特性
4.12.2 生成器模式
4.12.3 制造汽车的流程
说明:使用生成器模式来制造一辆汽车。
1 | // 创建组装着(指挥者) |
4.13 命令
命令模式:在软件系统中,
行为请求者
与行为执行者
通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行记录、撤销/重做、事务
等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将行为请求者
与行为执行者
解耦,将行为抽象成对象,实现两者之间的松耦合,这就是我们这门课程所要讲的命令模式
。
4.13.1 电视机、遥控器与接收器之间的关系
- 遥控器与接收器并非必要的设备
- 接收器转换遥控器的信号
4.13.2 命令模式
- 命令的发送者和命令的执行者之间完全解耦
- 命令可以回退(撤销)
4.13.3 改变一个视图的明暗程度
1 |
|
4.14 组合
说明:将对象组合成树形结构以表示
部分-整体
的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。组合设计模式可以解决某些线性结构无法解决的难题(如编写文件夹目录结构等类似问题),掌握它具有很实用的意义。
4.14.1 树形结构
课程说明:讲解树形结构,并实现一个简易的二叉树,对于后续的组合设计模式的理解很有帮助。
- 树形结构原理
- 简易的二叉树
- 用递归遍历树形结构节点
Node.h
1 |
|
Node.m
1 |
|
ViewController.m
1 |
|
4.14.2 组合模式
Node.h
1 |
|
Node.m
1 |
|
ViewController.m
1 |
|
4.14.3 编写文件夹系统
4.15 迭代器
提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部表示,这种模式叫迭代器模式,
Cocoa
框架中的NSEnumerator
就是迭代器模式的具体实现。学习迭代器模式有助于理解线性表结构的常规使用,对于优化具备线性表结构的类有实际意义。
4.15.1 线性表
课程说明:讲解常用线性表基本原理,对于理解何为迭代器模式很有帮助。
线性表:线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的(注意,这句话只适用大部分线性表,而不是全部。比如,循环链表逻辑层次上也是一种线性表(存储层次上属于链式存储),但是把最后一个数据元素的尾指针指向了哨位结点)。
栈和队列就是典型的线性表
4.15.2 迭代器模式
课程说明:讲解迭代器模式基本原理,并简单介绍下
Cocoa
框架已经实现的迭代器。
1 | /* 系统自身的集合和迭代器 */ |
4.15.3 实现组合对象的迭代器
课程说明:实现一个组合对象的迭代器,并遍历出该组合对象中我们需要关心的元素。
1 | CustomUIView *customUIView = [[CustomUIView alloc] initWithFrame:self.view.bounds]; |